#include <iostream>
#include <cstdlib>
#include <pthread.h>
#include <arpa/inet.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>
#include <openssl/rsa.h>       /* SSLeay stuff */
#include <openssl/crypto.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

#include "Service.h"
#include "SecuredService.h"
#include "TypesCompatibility.h"

#define CERTF "certs/server.pem"
#define KEYF "certs/privkey.pem"

//////////////////////////////////////////////////
// IMPLEMENTACION DE LA HEBRA PARA CADA CLIENTE //
//////////////////////////////////////////////////

SecuredClientThread::SecuredClientThread(SSL* ssl_, int sfd, Service* s) : ClientThread(sfd, s)
{
	ssl = ssl_;
}

void SecuredClientThread::run()
{
	pthread_detach(pthread_self());
	
	int n;
	char buffer[256];
	std::string msg;
	
	while(1)
	{
		msg = "";
		while ((n = SSL_read(ssl,buffer,255)) > 0)
		{
			msg = msg + buffer;
			
			if (buffer[n-1] == '\0') //fin de cadena
				break;
		}
		
		if (n <= 0) //el cliente ha sido desconectado
			break;
		
		//reenvio del mensaje a todos los clientes
		((SecuredService*)service)->broadCast(msg);
	}
	
	((SecuredService*)service)->removeClient(ssl);
}

//////////////////////////////////////////////////
//         IMPLEMENTACION DEL SERVICIO          //
//////////////////////////////////////////////////

SecuredService::SecuredService(int p, bool trc) : Service(p, trc)
{
	
}

SecuredService::~SecuredService()
{
	SSL_free (ssl);
	SSL_CTX_free (ctx);
}

void SecuredService::broadCast(const std::string& msg)
{
	pthread_mutex_lock(&mutex);
	
	//recorrer la lista de clientes conectados
	std::list<SSL*>::iterator it;
	for (it=ssl_clients.begin(); it!=ssl_clients.end(); ++it)
	{
		//enviar el mensaje
		SSL_write(*it, msg.c_str(), msg.length()+1);
	}
	
	pthread_mutex_unlock(&mutex);
}

void SecuredService::removeClient(SSL* ssl)
{
	std::list<SSL*>::iterator it;
	std::list<struct sockaddr_in>::iterator it_data;
	
	pthread_mutex_lock(&mutex);
	
	it_data = clients_data.begin();
	for (it = ssl_clients.begin(); it!=ssl_clients.end(); ++it)
	{
		if (*it == ssl)
		{
			ssl_clients.erase(it);
			break;
		}
		
		++it_data;
	}
	
	SSL_free(ssl);
	
	if (trace) std::cout << "Cliente desconectado [" << inet_ntoa((*it_data).sin_addr) << "]" << std::endl;
	
	clients_data.erase(it_data);
	
	pthread_mutex_unlock(&mutex);
}

void SecuredService::run()
{
	int newsockfd, err;
	socklen_t clilen;
	SecuredClientThread* ct; //hebras para cada cliente
	SSL_METHOD *meth;
	
	char buffer[256];
	struct sockaddr_in serv_addr, cli_addr;
	
	SSL_load_error_strings();
	SSLeay_add_ssl_algorithms();
	meth = SSLv23_server_method();
	ctx = SSL_CTX_new (meth);
	if (!ctx) 
	{
		ERR_print_errors_fp(stderr);
	    exit(1);
	}

	if (SSL_CTX_use_certificate_file(ctx, CERTF, SSL_FILETYPE_PEM) <= 0) {
		std::cerr << "ERROR lectura del fichero de certificado" << std::endl;
	    ERR_print_errors_fp(stderr);
	    exit(1);
	}
	if (SSL_CTX_use_PrivateKey_file(ctx, KEYF, SSL_FILETYPE_PEM) <= 0) {
		std::cerr << "ERROR lectura del fichero de clave privada" << std::endl;
	    ERR_print_errors_fp(stderr);
	    exit(1);
	}

	if (!SSL_CTX_check_private_key(ctx)) 
	{
	    std::cerr << "ERROR clave privada no asociable a clave publica del certificado" << std::endl;
	    exit(1);
	}
	
	
	sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd < 0)
	{
		std::cerr << "Error abriendo el socket" << std::endl;
		exit(1);
	}

	bzero((char *) &serv_addr, sizeof(serv_addr));
	
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	serv_addr.sin_port = htons(port);
	if (bind(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0)
	{
		std::cerr << "Error en el proceso de binding" << std::endl;
		exit(1);
	}
	
	listen(sockfd,5);
	
	if (trace) 
		std::cout << "Servicio iniciado correctamente [" << inet_ntoa(serv_addr.sin_addr) << ":" << port << "]" << std::endl;
	
	while(1)
	{
		newsockfd = accept(sockfd, 
					   (struct sockaddr*)&cli_addr, 
					   &clilen);
					
		if (newsockfd >= 0)
		{
			SSL* cli_ssl = SSL_new(ctx);
			if (cli_ssl > 0)
			{
				SSL_set_fd(cli_ssl, newsockfd);
				err = SSL_accept(cli_ssl);
				if (err < 0)
				{
					std::cerr << "ERROR conexion a cliente SSL" << std::endl;
					close(newsockfd);
				}
				else
				{
					pthread_mutex_lock(&mutex);
					
					clients_data.push_back(cli_addr);
					ssl_clients.push_back(cli_ssl);
					
					pthread_mutex_unlock(&mutex);

					if (trace) std::cout << "Nuevo cliente conectado [" << inet_ntoa(cli_addr.sin_addr) << "]" << std::endl;
					
					ct = new SecuredClientThread(cli_ssl, newsockfd, this);
					ct->start();
				}
			}
		}
	}
}

